﻿using System;
using System.Collections.Generic;
using System.Text;

using System.Xml;
using System.Threading;
using System.Net;

using System.Data;

namespace Transformer.iHunter.Plugins.BBS
{
    public class BbsChannel : Transformer.iHunter.IChannel
    {
		private BbsClient client;
		private BbsHost host;
		private List<string> boards;
		private bool updateMailbox;

		private SortedDictionary<Post, DateTime> postUpdateTime;
        private SortedDictionary<Post, Post> postUpdateRecord;

        private DataSet dataSet;
        DataTable dataTable;

        //private Dictionary<string, DateTime> updateTimes;

        // For Debug
        /*public BbsChannel(BbsHost host)
        {
            this.host = host;
            client = new BbsClient(host);

            boards = new List<string>();            
            updateMailbox = true;

            this.postUpdateTime = new SortedDictionary<Post, DateTime>();
            this.postUpdateRecord = new SortedDictionary<Post, Post>();
        }*/

		public BbsChannel(string settings, DataSet data)
		{
            this.dataSet = data;

			XmlDocument doc = new XmlDocument();

			doc.LoadXml(settings);

			XmlElement root = doc["BbsChannelSettings"];

			ConnectionType conType = ConnectionType.Telnet;
			if ("True" == root["SSH"].InnerText)
				conType = ConnectionType.SSH1;

			HostType hostType = HostType.SMTH;
			if ("Net9" == root["Type"].InnerText)
				hostType = HostType.Net9;

			host = new BbsHost(
					root["Address"].InnerText,
					root["Username"].InnerText,
					root["Password"].InnerText,
					int.Parse(root["Port"].InnerText),
					conType,
					hostType
				);

            client = new BbsClient(host);

            client.ErrorHandler += this.OnErrorOccur;

			boards = new List<string>();

			string boardsStr = root["Boards"].InnerText.Trim();
			if (boardsStr.Length > 0)
			{
				string[] brds = boardsStr.Split(
					new char[] { ';' },
					StringSplitOptions.RemoveEmptyEntries
					);

				if (null != brds && brds.Length > 0)
				{
					foreach (string brd in brds)
					{
						string board = brd.Trim();
						if (!boards.Contains(board))
							boards.Add(board);
					}
				}
			}

			updateMailbox = ("True" == root["Mailbox"].InnerText);

			this.postUpdateTime = new SortedDictionary<Post, DateTime>();
            this.postUpdateRecord = new SortedDictionary<Post, Post>();

            //GetUpdateTimes();
		}

        /*private void GetUpdateTimes()
        {
            this.updateTimes = new Dictionary<string, DateTime>();

            
            if (!dataSet.Tables.Contains("UpdateTime"))
            {
                dataTable = dataSet.Tables.Add("UpdateTime");

                List<DataColumn> primaryCols = new List<DataColumn>();

                primaryCols.Add(dataTable.Columns.Add("Address", typeof(string)));
                primaryCols.Add(dataTable.Columns.Add("Username", typeof(string)));
                primaryCols.Add(dataTable.Columns.Add("Board", typeof(string)));
                dataTable.Columns.Add("LastUpdate", typeof(DateTime));

                dataTable.PrimaryKey = primaryCols.ToArray();
            }
            else
            {
                dataTable = dataSet.Tables["UpdateTime"];
            }

            string query_prefix = string.Format("ADDRESS='{0}' AND USERNAME='{1}' AND ", 
                this.host.HostAddress, this.host.Username);

            if (updateMailbox)
            {
                DateTime lastUpdate = DateTime.MinValue;

                string query = query_prefix + string.Format("BOARD='{0}'", Mail.MailCategory);

                DataRow[] rows = dataTable.Select(query);

                if (null != rows && rows.Length > 0)
                {
                    lastUpdate = (DateTime)rows[0]["LastUpdate"];
                }

                this.updateTimes[Mail.MailCategory] = lastUpdate;
            }

            foreach (string board in this.boards)
            {
                DateTime lastUpdate = DateTime.MinValue;

                string query = query_prefix + string.Format("BOARD='{0}'", board);

                DataRow[] rows = dataTable.Select(query);

                if (null != rows && rows.Length > 0)
                {
                    lastUpdate = (DateTime)rows[0]["LastUpdate"];
                }

                this.updateTimes[board] = lastUpdate;
            }
        }

        private void RecordUpdateTime(string board, DateTime time)
        {
            updateTimes[board] = time;

            string query = string.Format("ADDRESS='{0}' AND USERNAME='{1}' AND BOARD='{2}'",
                this.host.HostAddress, this.host.Username, board);

            DataRow[] rows = dataTable.Select(query);

            if (null == rows || rows.Length == 0)
            {
                DataRow row = dataTable.NewRow();
                row["Address"] = this.host.HostAddress;
                row["Username"] = this.host.Username;
                row["Board"] = board;
                row["LastUpdate"] = time;

                dataTable.Rows.Add(row);
            }
            else
            {
                rows[0]["LastUpdate"] = time;
            }
        }*/

        private DownloadPostsTask GetDownloadPostsTask(string board, DateTime startFrom)
        {            
            if (board.Contains("("))
            {
                int ind = board.IndexOf('(');
                string boardName = board.Substring(0, ind).Trim();
                string atti = board.Substring(ind);
                DownloadPostsTask.Option opt_attach = DownloadPostsTask.Option.NotCare;
                DownloadPostsTask.Option opt_origPost = DownloadPostsTask.Option.NotCare;
                DownloadPostsTask.SignOption opt_sign = DownloadPostsTask.SignOption.NotCare;

                if (atti.Contains("Attach"))
                {
                    opt_attach = DownloadPostsTask.Option.Yes;
                }

                if (atti.Contains("FirstPost"))
                {
                    opt_origPost = DownloadPostsTask.Option.Yes;
                }

                if (atti.Contains("M_only"))
                {
                    opt_sign = DownloadPostsTask.SignOption.M;
                }
                else if (atti.Contains("G_only"))
                {
                    opt_sign = DownloadPostsTask.SignOption.G;
                }
                else if (atti.Contains("M_and_G"))
                {
                    opt_sign = DownloadPostsTask.SignOption.B;
                }
                else if (atti.Contains("M_or_G"))
                {
                    opt_sign = DownloadPostsTask.SignOption.MorG;
                }

                return new DownloadPostsTask(boardName, startFrom, string.Empty, string.Empty, opt_origPost, opt_sign, opt_attach,
                    DownloadPostsTask.Option.NotCare);
            }
            else
            {
                return new DownloadPostsTask(board.Trim(), startFrom);
            }
        }

		#region IChannel Members

		public IEnumerable<IMessage> DownloadMessages(DateTime startTime, DateTime endTime,string attachmentDirectoryPath)
		{
			//List<IMessage> messages = new List<IMessage>();

            System.Diagnostics.Debug.WriteLine("StartTime = " + startTime);
			
			if (updateMailbox)
			{
                DateTime startFrom = startTime;
//                 if (updateTimes[Mail.MailCategory] > startTime)
//                     startFrom = updateTimes[Mail.MailCategory];

                System.Diagnostics.Debug.WriteLine("Mail starts from " + startFrom);

				IEnumerable<Mail> mails = client.DownloadMails(new DownloadMailsTask(startFrom, DownloadMailsTask.MailFolder.Inbox));

                DateTime updateTime = DateTime.MinValue;
                DateTime startDown = DateTime.Now;

                foreach (Mail mail in mails)
                {
                    IMessage msg = mail.ToMessage();
                    msg.UpdateTime = msg.PublishTime;

                    if (msg.PublishTime.CompareTo(updateTime) > 0)
                        updateTime = msg.PublishTime;

                    System.Diagnostics.Debug.WriteLine("Downloaded "+msg.Author+" "+msg.Title);

                    yield return msg;
                }

                if (updateTime.CompareTo(DateTime.MinValue) == 0)
                {
                    updateTime = startDown;
                }

                //RecordUpdateTime(Mail.MailCategory, updateTime);
			}

            System.Diagnostics.Debug.WriteLine("Mail Done");

			foreach (string board in boards)
			{
                DateTime startFrom = startTime;
//                 if (updateTimes[board] > startTime)
//                     startFrom = updateTimes[board];

                System.Diagnostics.Debug.WriteLine("Mail starts from " + startFrom);

                DownloadPostsTask downTask = GetDownloadPostsTask(board, startFrom);
				IEnumerable<Post> posts = client.DownloadPosts(downTask);

                DateTime updateTime = DateTime.MinValue;
                DateTime startDown = DateTime.Now;

                foreach (Post post in posts)
                {
                    IMessage msg = post.ToMessage();
                    msg.UpdateTime = msg.PublishTime;
                    msg.Category = downTask.Board;

                    if (msg.PublishTime.CompareTo(updateTime) > 0)
                        updateTime = msg.PublishTime;

                    System.Diagnostics.Debug.WriteLine("Downloaded " + msg.Author + " " + msg.Title);

                    yield return msg;
                }

                if (updateTime.CompareTo(DateTime.MinValue) == 0)
                {
                    updateTime = startDown;
                }

                //RecordUpdateTime(board, updateTime);
			}
                        
            

            System.Diagnostics.Debug.WriteLine("Download done");

            yield break;
            
		}

        private void UpdatePost(IMessage message)
        {
            Post post = new Post(message);

            Monitor.Enter(postUpdateTime);
            if (postUpdateTime.ContainsKey(post))
            {
                DateTime time = postUpdateTime[post].AddMinutes(10);
                postUpdateTime.Remove(post);
                Post p = postUpdateRecord[post];
                postUpdateRecord.Remove(post);                

                if (time.CompareTo(DateTime.Now) >= 0)
                {

                    Monitor.Exit(postUpdateTime);

                    if ((!message.CustomFields["PlainText"].Equals(p.PlainText)) || (!message.Title.Equals(p.Title)))
                    {
                        DateTime upTime = DateTime.Now;
                        if (upTime.CompareTo(message.PublishTime) >= 0)
                            message.UpdateTime = upTime;
                        message.Read = false;
                    }

                    message.Title = p.Title;
                    message.Content = p.HtmlText;
                    message.CustomFields["PlainText"] = p.PlainText;

                    System.Diagnostics.Debug.WriteLine("Cached");

                    
                    return;
                }
            }
            Monitor.Exit(postUpdateTime);

            DownloadPostsTask task = new DownloadPostsTask(post.Board, post.PostTime, "", post.Author,
                DownloadPostsTask.Option.NotCare,
                DownloadPostsTask.SignOption.NotCare,
                DownloadPostsTask.Option.NotCare,
                DownloadPostsTask.Option.No
                );

            IEnumerable<Post> posts = client.DownloadPosts(task);

            DateTime updateTime = DateTime.Now;

            foreach (Post p in posts)
            {
                if (post.CompareTo(p) == 0)
                {
                    if ((!message.CustomFields["PlainText"].Equals(p.PlainText)) || (!message.Title.Equals(p.Title)))
                    {
                        if (updateTime.CompareTo(message.PublishTime) >= 0)
                            message.UpdateTime = updateTime;
                        message.Read = false;
                    }

                    message.Title = p.Title;
                    message.Content = p.HtmlText;
                    message.CustomFields["PlainText"] = p.PlainText;
                }
                else
                {
                    Monitor.Enter(postUpdateTime);
                    postUpdateTime[p] = updateTime;
                    postUpdateRecord[p] = p;
                    Monitor.Exit(postUpdateTime);
                }
            }
            
        }

        public IEnumerable<IMessage> UpdateMessage(IMessage message)
        {
            System.Diagnostics.Debug.WriteLine("Update!");

            if (Mail.MailCategory == message.Category)
                yield break;
            else
            {
                if (message.Title.StartsWith("Re:") || message.Title.StartsWith("Re："))
                {
                    UpdatePost(message);
                }
                else
                {
                    DownloadPostsTask task = new DownloadPostsTask(
                        message.Category,
                        message.PublishTime,
                        message.Title,
                        string.Empty,
                        DownloadPostsTask.Option.NotCare,
                        DownloadPostsTask.SignOption.NotCare,
                        DownloadPostsTask.Option.NotCare,
                        DownloadPostsTask.Option.NotCare
                        );

                    bool firstPostUpdated = false;

                    IEnumerable<Post> posts = client.DownloadPosts(task);

                    foreach (Post p in posts)
                    {
                        System.Diagnostics.Debug.WriteLine(string.Format("UpdateDown: {0}, {1}, {2}",
                            p.Author, p.Title, p.PostTime));
                        //if (p.Title.EndsWith(message.Title) && (p.Title.StartsWith("Re:") || p.Title.StartsWith("Re：")))
                        if (p.Title.TrimEnd().Equals(("Re: " + message.Title).TrimEnd()) || 
                            p.Title.TrimEnd().Equals(("Re：" + message.Title).TrimEnd()) ||
                            p.Title.TrimEnd().Equals(("Re: "+ message.Title).TrimEnd()+" (转载)"))
                        {
                            IMessage msg = p.ToMessage();
                            msg.Category = message.Category;
                            msg.UpdateTime = p.PostTime;

                            yield return msg;
                        }

                        if (p.Author.Equals(message.Author) && p.PostTime.CompareTo(message.PublishTime) == 0)
                        {
                            firstPostUpdated = true;

                            if ((!message.CustomFields["PlainText"].Equals(p.PlainText)) || (!message.Title.Equals(p.Title)))
                            {
                                DateTime updateTime = DateTime.Now;
                                if (updateTime.CompareTo(message.PublishTime) >= 0)
                                    message.UpdateTime = updateTime;
                                message.Read = false;
                            }

                            message.Title = p.Title;
                            message.Content = p.HtmlText;
                            message.CustomFields["PlainText"] = p.PlainText;
                        }
                    }

                    if (!firstPostUpdated)
                        UpdatePost(message);
                }

                yield break;
            }
        }

		public bool UpdateMessageOld(IMessage message)
		{
            System.Diagnostics.Debug.WriteLine("Update!");

			if (Mail.MailCategory == message.Category)
				return true;
			else
			{
				Post post = new Post(message);

				Monitor.Enter(postUpdateTime);
				if (postUpdateTime.ContainsKey(post))
				{
					DateTime time = postUpdateTime[post].AddMinutes(10);
					postUpdateTime.Remove(post);
                    Post p = postUpdateRecord[post];
                    postUpdateRecord.Remove(post);

					if (time.CompareTo(DateTime.Now) >= 0)
					{
                        
                        if ((!message.Content.Equals(p.HtmlText)) || (!message.Title.Equals(p.Title)))
                        {
                            DateTime upTime = DateTime.Now;
                            if (upTime.CompareTo(message.PublishTime) >= 0)
                                message.UpdateTime = upTime;
                            message.Read = false;
                        }

                        message.Title = p.Title;
                        message.Content = p.HtmlText;
                        message.CustomFields["PlainText"] = p.PlainText;

                        System.Diagnostics.Debug.WriteLine("Cached");

						Monitor.Exit(postUpdateTime);
						return true;
					}
				}
				Monitor.Exit(postUpdateTime);

				DownloadPostsTask task = new DownloadPostsTask(post.Board, post.PostTime, "", post.Author, 
					DownloadPostsTask.Option.NotCare,
					DownloadPostsTask.SignOption.NotCare,
					DownloadPostsTask.Option.NotCare,
					DownloadPostsTask.Option.No
					);

				IEnumerable<Post> posts = client.DownloadPosts(task);

				DateTime updateTime = DateTime.Now;

				foreach (Post p in posts)
				{
					if (post.CompareTo(p) == 0)
					{
                        if ((!message.Content.Equals(p.HtmlText)) || (!message.Title.Equals(p.Title)))
                        {
                            if (updateTime.CompareTo(message.PublishTime) >= 0)
                                message.UpdateTime = updateTime;
                            message.Read = false;
                        }

						message.Title = p.Title;
						message.Content = p.HtmlText;
						message.CustomFields["PlainText"] = p.PlainText;
					}
					else
					{
						Monitor.Enter(postUpdateTime);
						postUpdateTime[p] = updateTime;
                        postUpdateRecord[p] = p;
						Monitor.Exit(postUpdateTime);
					}
				}

				return true;
			}
		}

        public bool SendMessage(IMessage message)
		{
			DateTime time = DateTime.Now.Subtract(new TimeSpan(1, 0, 0, 0));

			if (Mail.MailCategory == message.Category)
			{
				SendMailTask task = new SendMailTask(message.Receiver, message.Title, message.Content);
				if (client.SendMail(task))
				{
					IEnumerable<Mail> mails = client.DownloadMails(new DownloadMailsTask(time, DownloadMailsTask.MailFolder.Outbox));									

					foreach (Mail mail in mails)
					{
						if (message.Title.TrimEnd().Equals(mail.Title.TrimEnd()))
						{
                            mail.Receiver = message.Author;
							IMessage mailMsg = mail.ToMessage();
							message.Author = mailMsg.Author;
							message.Category = Mail.MailCategory;
							message.Content = mailMsg.Content;
							message.PublishTime = mailMsg.PublishTime;
							message.Title = mailMsg.Title;
							message.CustomFields["MailType"] = mailMsg.CustomFields["MailType"];
							message.CustomFields["PlainText"] = mailMsg.CustomFields["PlainText"];
							mailMsg = null;

							return true;
						}
					}					
				}
			}
			else
			{
				CreateThreadTask task = new CreateThreadTask(message.Category, message.Title, message.Content);
				if (client.CreateThread(task))
				{
					IEnumerable<Post> posts = client.DownloadPosts(new DownloadPostsTask(message.Category, time, message.Title, host.Username,
						DownloadPostsTask.Option.NotCare,
                        DownloadPostsTask.SignOption.NotCare,
						DownloadPostsTask.Option.NotCare,
						DownloadPostsTask.Option.No));

					foreach (Post post in posts)
					{
						if (message.Title.TrimEnd().Equals(post.Title.TrimEnd()))
						{							
							message.Author = post.Author;
							message.Category = post.Board;
							message.Content = post.HtmlText;
							message.PublishTime = post.PostTime;
							message.Title = post.Title;
							message.CustomFields["PlainText"] = post.PlainText;

							return true;
						}
					}
				}
			}

			return false;
		}

		public bool ReplyMessage(IMessage orginal, IMessage reply)
		{
			DateTime time = DateTime.Now.Subtract(new TimeSpan(1, 0, 0, 0)); 

			if (Mail.MailCategory == orginal.Category)
			{
				Mail origMail = new Mail(orginal);
				ReplyMailTask task = new ReplyMailTask(origMail, reply.Title, reply.Content);

				if (client.ReplyMail(task))
				{
					if (time.CompareTo(origMail.SendTime) < 0)
						time = origMail.SendTime;

					IEnumerable<Mail> mails = client.DownloadMails(new DownloadMailsTask(time, DownloadMailsTask.MailFolder.Outbox));

					foreach (Mail mail in mails)
					{
						if (reply.Title.TrimEnd().Equals(mail.Title.TrimEnd()))
						{
                            mail.Receiver = origMail.Sender;
							IMessage mailMsg = mail.ToMessage();
							reply.Author = mailMsg.Author;
							reply.Category = Mail.MailCategory;
							reply.Content = mailMsg.Content;
							reply.PublishTime = mailMsg.PublishTime;
							reply.Title = mailMsg.Title;
							reply.CustomFields["MailType"] = mailMsg.CustomFields["MailType"];
							reply.CustomFields["PlainText"] = mailMsg.CustomFields["PlainText"];
							mailMsg = null;

							return true;
						}
					}					
				}
			}
			else
			{
				Post origPost = new Post(orginal);
				ReplyPostTask task = new ReplyPostTask(origPost.Board, origPost, reply.Title, reply.Content);

				if (client.ReplyPost(task))
				{
					if (time.CompareTo(origPost.PostTime) < 0)
						time = origPost.PostTime;

					IEnumerable<Post> posts = client.DownloadPosts(new DownloadPostsTask(origPost.Board, time, reply.Title, host.Username,
						DownloadPostsTask.Option.NotCare,
                        DownloadPostsTask.SignOption.NotCare,
						DownloadPostsTask.Option.NotCare,
						DownloadPostsTask.Option.No));

					foreach (Post post in posts)
					{
						if (reply.Title.TrimEnd().Equals(post.Title.TrimEnd()))
						{
							reply.Author = post.Author;
							reply.Category = post.Board;
							reply.Content = post.HtmlText;
							reply.PublishTime = post.PostTime;
							reply.Title = post.Title;
							reply.CustomFields["PlainText"] = post.PlainText;

							return true;
						}
					}
				}
			}

			return false;
		}

		public void CancelBlockingCall()
		{
            Monitor.Enter(this);
            client.CancelAllTasks();
            client.Dispose();
            client = null;
            client = new BbsClient(host);
            Monitor.Exit(this);
		}

		public event PluginErrorEventHandler ErrorOccur;

        private void OnErrorOccur(object sender, PluginErrorEventArgs e)
        {
            if (this.ErrorOccur != null)
                this.ErrorOccur(this, e);
        }

        public bool DownloadAttachment(IAttachment attachment)
        {
            try
            {
                WebClient client = new WebClient();
                client.DownloadFile(attachment.RemoteUrl, attachment.LocalPath);
            }
            catch
            {
                return false;
            }

            return true;
        }

        #endregion

        #region IDisposable 成员

        public void Dispose()
        {
            Monitor.Enter(this);
            client.CancelAllTasks();
            client.Dispose();
            client = null;            
            Monitor.Exit(this);
        }

        #endregion
    }
}
